package simpledb.file;

import static simpledb.file.Page.BLOCK_SIZE;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.*;

/**
 * The SimpleDB file manager. The database system stores its data as files
 * within a specified directory. The file manager provides methods for reading
 * the contents of a file block to a Java byte buffer, writing the contents of a
 * byte buffer to a file block, and appending the contents of a byte buffer to
 * the end of a file. These methods are called exclusively by the class
 * {@link simpledb.file.Page Page}, and are thus package-private. The class also
 * contains two public methods: Method {@link FileMgr#isNew() isNew} is called during
 * system initialization by {@link simpledb.server.SimpleDB#init}. Method
 * {@link #size(String) size} is called by the log manager and transaction
 * manager to determine the end of the file.</br>
 * SimpleDB文件管理器。 数据库系统将其数据作为文件存储在指定的目录中。 文件管理器提供了读取文件块的内容的方法 到Java字节缓冲区，
 * 将字节缓冲区的内容写入文件块， 并将字节缓冲区的内容附加到文件的末尾。 这些方法仅由{@link simpledb.file.Page Page}类调用，
 * ，因此是包私有的。 该类还包含两个公共方法： 方法{@link FileMgr#isNew() isNew}在系统初始化期间通过
 * {@link simpledb.server.SimpleDB#init}调用。 方法{@link FileMgr#size(String) size}
 * 由日志管理器和事务管理器调用 确定文件的结尾。
 * 
 * @author Edward Sciore
 */
public class FileMgr {
  private File dbDirectory;
  private boolean isNew;
  private Map<String, FileChannel> openFiles = new HashMap<String, FileChannel>();

  /**
   * Creates a file manager for the specified database. The database will be
   * stored in a folder of that name in the user's home directory. If the folder
   * does not exist, then a folder containing an empty database is created
   * automatically. Files for all temporary tables (i.e. tables beginning with
   * "temp") are deleted. </br>
   * 为指定的数据库创建文件管理器。 数据库将存储在用户主目录中该名称的文件夹中。 如果文件夹不存在，则会自动创建包含空数据库的文件夹。
   * 删除所有临时表(即以“temp”开头的表)的文件。
   * 
   * @param dbname the name of the directory that holds the database
   */
  public FileMgr(String dbname) {
    String homedir = System.getProperty("simple.db.home");
    if(homedir == null) {
      homedir = System.getProperty("user.home");;
    }
    dbDirectory = new File(homedir, dbname);
    isNew = !dbDirectory.exists();

    // create the directory if the database is new
    if (isNew && !dbDirectory.mkdir())
      throw new RuntimeException("cannot create " + dbname);

    // remove any leftover temporary tables
    for (String filename : dbDirectory.list())
      if (filename.startsWith("temp"))
        new File(dbDirectory, filename).delete();
  }

  /**
   * Reads the contents of a disk block into a bytebuffer.<br>
   * 将磁盘块的内容读取到字节缓冲区。
   * 
   * @param blk a reference to a disk block
   * @param bb the bytebuffer
   */
  synchronized void read(Block blk, ByteBuffer bb) {
    try {
      bb.clear();
      FileChannel fc = getFile(blk.fileName());
      fc.read(bb, blk.number() * BLOCK_SIZE);// 从给定的文件位置开始，从此通道读取字节序列,并写入给定的缓冲区。
    } catch (IOException e) {
      throw new RuntimeException("cannot read block " + blk);
    }
  }

  /**
   * Writes the contents of a bytebuffer into a disk block.<br>
   * 将字符缓冲区的内容写入磁盘块。
   * 
   * @param blk a reference to a disk block
   * @param bb the bytebuffer
   */
  synchronized void write(Block blk, ByteBuffer bb) {
    try {
      bb.rewind();// 回退此缓冲区。 位置设置为零，标记将被丢弃。
      FileChannel fc = getFile(blk.fileName());
      fc.write(bb, blk.number() * BLOCK_SIZE);// 从给定的文件位置开始，将字节序列从给定缓冲区写入此通道。
    } catch (IOException e) {
      throw new RuntimeException("cannot write block" + blk);
    }
  }

  /**
   * Appends the contents of a bytebuffer to the end of the specified file.<br>
   * 将字节缓冲区的内容附加到指定文件的末尾。
   * 
   * @param filename the name of the file
   * @param bb the bytebuffer
   * @return a reference to the newly-created block.
   */
  synchronized Block append(String filename, ByteBuffer bb) {
    int newblknum = size(filename);//每个块为400KB,取得这是第几块
    Block blk = new Block(filename, newblknum);
    write(blk, bb);
    return blk;
  }

  /**
   * Returns the number of blocks in the specified file.<br>
   * 返回指定文件中的Block的个数。
   * 
   * @param filename the name of the file
   * @return the number of blocks in the file
   */
  public synchronized int size(String filename) {
    try {
      FileChannel fc = getFile(filename);
      return (int) (fc.size() / BLOCK_SIZE);
    } catch (IOException e) {
      throw new RuntimeException("cannot access " + filename);
    }
  }

  /**
   * Returns a boolean indicating whether the file manager had to create a new
   * database directory. <br>
   * 返回一个布尔值，指示文件管理器是否必须创建新的数据库目录。
   * 
   * @return true if the database is new
   */
  public boolean isNew() {
    return isNew;
  }

  /**
   * Returns the file channel for the specified filename. The file channel is
   * stored in a map keyed on the filename. If the file is not open, then it is
   * opened and the file channel is added to the map.<br>
   * 返回指定文件名的文件通道。 文件通道存储在以文件名命名的Map中。 如果文件未打开，则会打开该文件，并将文件通道添加到Map中。
   * 
   * @param filename the specified filename
   * @return the file channel associated with the open file.
   * @throws IOException
   */
  private FileChannel getFile(String filename) throws IOException {
    FileChannel fc = openFiles.get(filename);
    if (fc == null) {
      File dbTable = new File(dbDirectory, filename);
      RandomAccessFile f = new RandomAccessFile(dbTable, "rws");
      fc = f.getChannel();
      openFiles.put(filename, fc);
    }
    return fc;
  }
}
